-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more integer atomic types #1543
Conversation
The rendered link is wrong. |
Fixed |
Thanks for writing this up @Amanieu! I'm personally would prefer to take this strategy over #1505 as it's more faithful to the atomics we have today (in terms of implementation strategy). There are some small tweaks, however, that I might also change:
|
Regarding
Are you asking for something like |
I was persuaded by @Amanieu's comment that there's so much variance within one architecture (e.g. ARM) that we shouldn't have something that is tied to the architecture but rather support via |
I believe this is addressed in the "Target support" section.
The reason for this was to support
I don't really mind, there are plenty of opportunities for bikeshedding here.
In my view
As I said above, my idea is that libcore supports all platforms, whereas libstd requires a certain minimal feature set to function. |
@alexcrichton so |
Ah sorry looks like I missed that part of the RFC, my mistake! I wonder though if the
Yeah in some sense this is true, but platforms supporting pointer-sized atomics seems very common where supporting, for example, 64-bit atomics is much less common. I don't think we'd want usage of
Ah yes, thanks for pointing this out again! My opinion is that Yeah something along those lines (although the names I'd certainly be willing to debate). |
Yesterday the libs team was discussing rust-lang/rust#32365 and an interesting point came up. @brson remembered an article in the past which outlined how bitfields can cause problems in C with respect to atomics and word sizes. This is mostly connected to bitfields I think, but it's an interesting data point about smaller-than-word-size atomics using word-size instructions with compare-and-swap to emulate an atomic operation. My digestion of that article, however, is that it doesn't likely impact Rust for now, as we don't have bitfields. Additionally, if we did have bitfields, it seems like we probably wouldn't want those semantics for other reasons? |
That gcc bug ( https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52080 ) was that a larger than required read-modify-write was used, which resulted in a non-atomic read-modify-write of a field marked volatile in a struct. It happened to trigger due to some bad handling of bitfields, but the basic concept still exists without them: imagine a machine that can't load/store less than 32bits at a time. In the article, most of the issue seems to have come from C not having completely specified behavior (ie: the C standard current allows a bit too much flexibility) for I suppose our answer to this would mostly hinge on:
|
I think this bug was a case of gcc specifically violating the C++ memory model which forbids a store to one memory location from affecting another memory location. This is explained in this comment which comes from the fix for that gcc bug. /* In the C++ memory model, consecutive bit fields in a structure are
considered one memory location and updating a memory location
may not store into adjacent memory locations. */ An architecture that is unable to generate byte-level stores is fundamentally incompatible with the C++ memory model, and would therefore not be supported by Rust since we depend on that memory model. The only way that an architecture which only has 32-bit stores could be supported in C would be to make |
This isn't actually a problem in this case because the compare-and-swap is atomic, which means that another thread reading data near the emulated atomic will never see an incorrect value and will never have a write which races with the atomic write. This is why this transformation performed by LLVM is valid and does not violate the C++ memory model. The only problematic situation is when a non-atomic read-modify-write operation is performed because then a store by a second thread could be "lost" if it occurs while another thread is accessing nearby data. The good news is that all architectures that support the C++ memory model must support byte-level stores, therefore having Rust rely on this feature is perfectly fine. |
Here is a rough idea of what the implementation of a generic struct Atomic<T>(UnsafeCell<T>);
impl<T: Copy> Atomic<T> {
pub fn swap(&self, val: T, order: Ordering) -> T {
#[cfg(target_has_atomic = "128")]
if mem::size_of::<T>() == 16 && mem::align_of::<T>() >= 16 {
let a = &*(self as *const Atomic<T> as *const AtomicU128);
return mem::transmute(a.swap(mem::transmute(val), order));
}
#[cfg(target_has_atomic = "64")]
if mem::size_of::<T>() == 8 && mem::align_of::<T>() >= 8 {
let a = &*(self as *const Atomic<T> as *const AtomicU64);
return mem::transmute(a.swap(mem::transmute(val), order));
}
#[cfg(target_has_atomic = "32")]
if mem::size_of::<T>() == 4 && mem::align_of::<T>() >= 4 {
let a = &*(self as *const Atomic<T> as *const AtomicU32);
return mem::transmute(a.swap(mem::transmute(val), order));
}
#[cfg(target_has_atomic = "16")]
if mem::size_of::<T>() == 2 && mem::align_of::<T>() >= 2 {
let a = &*(self as *const Atomic<T> as *const AtomicU16);
return mem::transmute(a.swap(mem::transmute(val), order));
}
#[cfg(target_has_atomic = "8")]
if mem::size_of::<T>() == 1 {
let a = &*(self as *const Atomic<T> as *const AtomicU8);
return mem::transmute(a.swap(mem::transmute(val), order));
}
let result: T = mem::uninitialized();
atomic_swap_fallback(self.0.get() as *mut (), &val as *const (), &mut result as *mut (), mem::size_of::<T>());
result
}
} |
I just released a crate which uses this to implement a generic |
Nice work @Amanieu! I'm gonna push for this to move into FCP this week hopefully |
🔔 This RFC is now entering its week-long final comment period 🔔 |
I personally am in favor of this RFC as an approach to handling differently sized atomics. In terms of technical details, I've got two pretty minor reservations:
I'd personally propose |
The RFC states that 128-bit atomics depend on the
This still leaves the issue of what we should do about the existing atomic types on platforms that do not support any form of atomics. |
I don't think separate modules necessarily adds value for users of this API (while it may save some extra It also isn't clear how things like Placing atomics in |
Ah my mistake! Double-word compare and swaps, however, are pretty useful so I hear, so maybe we could get u64x2 today and maybe deprecate it in the future if u128 is stabilized?
Indeed! I should clarify my thinking there. I'm not really sure what the best course of action is here, but as I've said before I think our hands are basically tied by today's stabilization. I don't think we want to deprecate We can probably just bolster the documentation to indicate that platforms which don't have
It's essentially the same strategy as Similarly, with these atomics we'd basically just be indicating which platforms have support for these kinds of atomic operations. Only if the target indeed has For |
No, I couldn't. Nothing about With
No, it doesn't entirely clarify that. Are you saying that |
Yes, that's what I'd be thinking ( |
I wanted to cross-post a comment I just wrote elsewhere, related to APIs whose existence is platform/arch dependent. I just wanted to make a general point that we have a strong need for vision in the area of arch/OS/config/...-dependent APIs. Examples include:
The common thread among all these problems is that we want to expose, in libraries like But this clearly doesn't scale to other kinds of distinctions. Now, it's not obvious that all of the above examples can be solved by a single mechanism. But I am really eager to see some basic vision for how to approach these questions in Rust, before we go too far down the road of adding specific ad hoc APIs. If @Amanieu or anyone else is interested in this topic in general, I'd love to work together toward an RFC! That said, @alexcrichton has been making some concrete suggestions along these lines for the current RFC (via |
@aturon: One thing to consider - module hierarchies are a strict tree, but some functionality may well have fan-in, not just fan-out. As an example, an atomic float type would depend on both 32-bit atomics and float support - and one does not necessarily imply the other. This can be hacked around with As the first example that comes to mind, RISC-V has separate extensions for floating-point ( In fact, it's very likely that the minion cores on lowRISC will need atomics, and very unlikely they'll need floating-point - and as they'll be largely dedicated to being isolated coprocessors for running drivers, Rust's safety guarantees are very relevant. Similarly, DSPs are very likely to want floating point, but less likely to need atomics - and Rust's support for zero-cost abstractions would make it much easier to develop for them without sacrificing performance. As a result, I think it's probably most useful to have a namespace whose members are platform dependent, but not use the names to distinguish platform. They just don't match the non-hierarchical reality. |
cc @rust-lang/lang |
I think it is important to consider how these examples differ from each other:
(I'm not commenting on the Android ABI issue since I'm not familiar with it) In conclusion, I don't think there is much of a case for a common mechanism to handle all these cases. I think a namespace like |
@Amanieu Thanks -- I agree that there are substantial differences here. What I'm most interested in having a discussion about is what our overall goals are in terms of platform support/compatibility (which may vary in each of the cases we're discussing), and then the best mechanisms to support those goals. I put some time into this question last week, and have the outlines of a proposal. Unfortunately, I'm on vacation right now and won't be back for a week. I plan to kick of an internals discussion with full details when I get back. Regardless, though, I don't think these broad considerations should block the RFC from being accepted -- rather, they should block final stabilization, by which point I'd like to have these questions resolved anyway. I would propose for now that we accept the RFC as-is, that is, with the new atomic types living alongside the existing ones. If and when we have a more complete plan for arch-specific APIs that would demand a different placement, we can move them prior to stabilization. (FWIW, the plan I have in mind would not involve moving these APIs) |
#[cfg(target_has_atomic = "ptr")] | ||
struct AtomicUsize {} | ||
#[cfg(target_has_atomic = "ptr")] | ||
struct AtomicPtr<T> {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AtomicPtr, Isize and Usize can't be defined cfg(target_has_atomic)
because it's not backwards compatible. Please change this before landing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brson are you stating that because we've previously promised support for atomic ptr-size values, we're stuck supporting them for all platforms rust targets for the foreseeable future? In effect: if we ever want to support a platform without pointer-size atomics, it will require compiler-rt provide some AtomicPtr work-alike?
As the RFC notes later, all architectures we currently support would be unaffected. Only new, not yet existent archs would be broken. I'm presuming you don't think this is sufficient?
Is there some other process you're implicitly proposing here that I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmesmon I think the ship has sailed on supporting ptr-sized atomics conditionally. We already guarantee support on platforms that don't have atomics.
This is a pretty bad situation though because our atomics are not guaranteed to be lock free.
Saving grace may be that no architectures without atomics are tier-1 so maybe we can roll back these prior decisions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saving grace may be that no architectures without atomics are tier-1 so maybe we can roll back these prior decisions.
To be precise all of the targets in tier 1, 2 and 3 support atomic operations. The only way to have a target that doesn't support atomics is through a custom target spec. And even in these cases I think attempts to use atomic operations will result in a linker error due to missing functions in compiler-rt (compiler-rt uses spinlocks to implement atomic operations, which themselves require support for atomic operations).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saving grace may be that no architectures without atomics are tier-1 so maybe we can roll back these prior decisions
Isn't this exactly what the RFC says (which I referred to previously)? Is there something I'm missing here? Or is this just an agreement? Maybe you're just noting that this is something that needs discussion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I ran a few tests, and here's the current situation for platforms that don't support atomic types (I tested with ARMv6-M):
- LLVM will convert
fetch_add
into a call to__sync_fetch_and_add_4
, which is normally provided by compiler-rt. - However compiler-rt itself uses atomic operations to implement
__sync_fetch_and_add_4
, so it fails to compile on such platforms anyways.
So basically, atomic operations on those platforms has never worked in the first place, so we really aren't breaking anything.
@sunfish mentioned to me that it's possible to define lock-free atomics for any size less than a pointer as long as you are willing to spin on the full word. This says to me that maybe it's not necessary to define things like |
@brson regarding spinning on smaller than atomic values, take a look at the discussion started in this comment above: #1543 (comment) I believe the result is that unless we're able to control data layout in such a way to forbid smaller-than-atomics treated as atomics from occupying the same atomic-unit as non-atomic values, spinning is insufficient. |
The libs team discussed this RFC during triage yesterday and the decision was to merge. We decided that the location in These types will require some extra scrutiny before stabilization, but that doesn't necessarily need to block them landing in the standard library at all, so we felt it was ok to merge and implement for now to let experimentation on nightly with the types. Thanks again for the RFC @Amanieu and discussion everyone! |
Tracking issue: rust-lang/rust#32976 |
Add integer atomic types Tracking issue: #32976 RFC: rust-lang/rfcs#1543 The changes to AtomicBool in the RFC are not included, they are in a separate PR (#32365).
Follow up: I've opened a discuss thread to talk about how we want to handle these APIs in general. |
Previous RFC: #1505
Relevant issues: #1125 rust-lang/rust#24564
Relevant discussion in internals
Rendered